<template>
  <el-tree
    ref="elTree"
    style="margin: 5px; width: 100%;"
    :lazy="lazy"
    :data="treeData"
    :props="props"
    :load="loadTreeNode"
    :expand-on-click-node="false"
    :highlight-current="true"
    :render-content="renderTreeContent"
    :draggable="draggable"
    :allow-drop="treeAllowDrop"
    node-key="key"
    :show-checkbox="showCheckbox"
    :default-expand-all="defaultExpandAll"
    :default-checked-keys="defaultCheckedKeys"
    :filter-node-method="filterNode"
    @current-change="handleTreeNodeChange"
    @node-drop="treeHandleDrop"
  />
</template>

<style>
  .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content { /* 修改选中当前节点的样式 */
    background-color: rgba(135, 206, 235, 0.3);
  }
</style>

<script>
export default {
  components: { },
  props: {
    // 目录标签，会组合成 新建目录
    catalogLabel: {
      type: String,
      default: '目录'
    },
    // 是否显示 checkbox
    showCheckbox: {
      type: Boolean,
      default: false
    },
    // 是否只读
    readonly: {
      type: Boolean,
      default: false
    },
    // 是否默认展开所有节点
    defaultExpandAll: {
      type: Boolean,
      default: false
    },
    // 是否可拖拽
    draggable: {
      type: Boolean,
      default: false
    },
    // 元素标签，会组合成 新建元素
    elementLabel: {
      type: String,
      default: '元素'
    },
    // 是否懒加载
    lazy: {
      type: Boolean,
      default: false
    },
    // tree 默认数据
    treeData: {
      type: Array,
      default: () => []
    },
    // 默认勾选的节点的 key 的数组
    defaultCheckedKeys: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      filterContent: { // tree 过滤条件
        editStatus: '',
        priority: ''
      },
      node_had: undefined, // tree 的根节点
      resolve_had: undefined, // tree 的根节点的 resolve
      currentNode: null, // 当前选中的 tree 节点
      currentNodeData: null, // 当前选中的 tree 节点的 data，当需要修改节点的 label 时使用
      props: { // tree 的 props 对象，用来映射新的 tree 属性
        label: 'name',
        children: 'children',
        isLeaf: 'leaf',
        key: 'id'
      },
      addNewNode: false // 添加新节点 - 标记
    }
  },
  watch: {
  },
  created() {
  },
  methods: {
    /**
     * 对树节点进行筛选操作
     * @param value
     */
    filter(value) {
      this.filterContent = value
      this.$refs.elTree.filter(value)
    },
    /**
     * 对树节点进行筛选时执行的方法
     * @param value
     * @param data
     * @returns {boolean} 返回 true 表示这个节点可以显示，返回 false 则表示这个节点会被隐藏
     */
    filterNode(value, data) {
      if (data.isCatalog) {
        return true
      }
      if ((!value.editStatus || value.editStatus === '') && (!value || value.priority === undefined || value.priority === '')) {
        return true
      }
      if (((!value.editStatus || value.editStatus === '') || (data.editStatus && data.editStatus === value.editStatus)) &&
          ((!value || value.priority === undefined || value.priority === '') || (data.priority !== undefined && data.priority === value.priority))) {
        return true
      } else {
        return false
      }
    },
    /**
     * 改变当前选中节点的数据
     * @param data
     */
    changeNode(data) {
      if (data instanceof Object) { // 对象，修改 label 和 icon
        if (Object.keys(data).includes('name')) this.currentNodeData.name = data.name
        if (Object.keys(data).includes('icon')) this.currentNodeData.icon = data.icon
        if (Object.keys(data).includes('priority')) this.currentNodeData.priority = data.priority
        if (Object.keys(data).includes('editStatus')) this.currentNodeData.editStatus = data.editStatus
      } else { // 字符串，修改 label
        this.currentNodeData.name = data
      }
      this.$refs.elTree.filter(this.filterContent) // 更新数据后，手动调用重新过滤
    },
    /**
     * 下一个节点
     */
    nextNode() {
      const node = this.getAfterNode(this.currentNodeData)
      if (node === null) {
        setTimeout(() => {
          this.$message({ message: '当前分类下已经没有下一个元素了!', type: 'warning' })
        }, 200)
      } else {
        this.$refs.elTree.setCurrentKey(node.data.key)
        this.handleTreeNodeChange(node.data, node)
      }
    },
    /**
     * 父组件通过调用此方法，更新当前 tree 节点。传入字符串更新 label，传入对象更新 label 和 icon
     * @param name 需要改成的 label 内容
     */
    updateNode(data) {
      this.changeNode(data)
    },
    /**
     * 父组件通过调用此方法，更新当前 tree 节点，并且选中下一个节点。传入字符串更新 label，传入对象更新 label 和 icon
     * @param name 需要改成的 label 内容
     */
    updateNextNode(data) {
      this.changeNode(data)

      this.nextNode()
    },
    /**
     * 获取当前目录中的后一个节点
     * @param parentNode 父节点
     * @param currentNodeData 当前节点 data 数据
     */
    getAfterNode(nodeData) {
      const parentNode = this.getParentNode(nodeData)

      for (let i = 0; i < parentNode.childNodes.length; i++) {
        if (parentNode.childNodes[i].data.key === nodeData.key) {
          if (i === (parentNode.childNodes.length - 1)) { // 最后一个节点，返回 null
            return null
          } else { // 不是最后一个节点，取下一个节点返回
            for (let j = i; j < parentNode.childNodes.length; j++) { // tree 增加过滤器后，可能会有隐藏节点，所以需要判断是否可见
              if (j === (parentNode.childNodes.length - 1)) { // 最后一个节点，返回 null
                return null
              } else {
                const nextNode = parentNode.childNodes[j + 1]
                if (nextNode.visible) {
                  return nextNode
                }
              }
            }
          }
        }
      }
    },
    /**
     * 根据 node 的 data 得到父节点 node
     * @param nodeData 当前节点的 data 数据
     */
    getParentNode(nodeData) {
      const tree = this.$refs.elTree // 得到 tree
      if (nodeData.parentId === 0) { // 一级节点，直接返回 root
        return tree.root
      } else { // 非一级节点根据 c-parentId 获取父节点
        return tree.getNode('c-' + nodeData.parentId)
      }
    },
    /**
     * 父组件通过调用此方法，给当前节点添加子节点
     * @param id 数据库中的 id
     * @param parentId 父分类 id
     * @param name tree 中显示的 label
     * @param isCatalog 是否是分类
     * @param priority 优先级
     * @param editStatus 用例编写状态
     */
    appendNode(id, parentId, name, isCatalog, priority, editStatus) {
      const newChild = { id: id, name: name, children: [], parentId: parentId, isCatalog: JSON.parse(isCatalog), priority: priority, editStatus: editStatus }
      if (parentId === 0) {
        this.resolve_had([newChild])
      } else {
        this.$refs.elTree.append(newChild, this.currentNode) // 这里每次加一个子节点，如果需要多个可以写多个
      }
      this.currentNodeData = newChild

      this.$refs.elTree.filter(this.filterContent) // 新增数据后，手动调用重新过滤
    },
    // sleep 方法
    async sleep(time) {
      return new Promise((resolve) => setTimeout(resolve, time))
    },
    /**
     * 设置当前选中节点
     * @param elementParent 节点数组，遍历该数组直到选中最后一个节点
     * @returns {Promise<void>}
     */
    async setCurrentNode(elementParent) {
      if (elementParent) {
        let i = 0
        for (const element of elementParent) {
          i++
          const node = this.$refs.elTree.getNode(element.key) // 根据 key 获取节点
          if (node) {
            if (!node.expanded) {
              if (i === elementParent.length) { // 节点没有展开，且是最后一个节点，选中该节点
                this.$refs.elTree.setCurrentKey(element.key)
                this.handleTreeNodeChange(node.data, node)

                // 将选中的节点，滚动到浏览器窗口的可见区域
                this.$nextTick(() => {
                  setTimeout(() => {
                    const currentNode = document.getElementsByClassName('el-tree-node is-current')
                    if (currentNode) {
                      currentNode[0].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' })
                    }
                  }, 250)
                })
              } else { // 节点没有展开，且不是最后一个节点，打开此节点
                node.expand()
                for (let i = 0; i < 20; i++) { // 阻塞在这里，等待节点展开完成
                  if (node.expanded) { break }
                  await this.sleep(50) // await
                }
              }
            }
          }
        }
      }
    },
    copyCatalog(node, id) {
      // TODO: 应该是不用下边两行，观察下没 bug 再删除
      // this.currentNode = node // 设置当前节点
      // this.addNewNode = true

      this.$confirm('是否复制 "' + node.label + '" 及其所有子元素?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$emit('copy-catalog', id, (element) => {
          this.$message({
            type: 'success',
            message: '复制成功!'
          })
          if (element.isCatalog) {
            this.appendNode(element.id, element.parentId, element.name, true, null, null)
          }
        })
      }).catch(() => {
      })
    },
    newCatalog(node, parent_id) {
      node.expand() // 展开当前节点
      this.addNewNode = true // 设置 添加新节点标记 为 true
      this.currentNode = node // 设置当前节点
      this.$emit('add-catalog', parent_id)
    },
    delCatalog(node, id) {
      this.currentNode = node // 设置当前节点
      this.addNewNode = true

      this.$confirm('是否删除 "' + node.label + '" ?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$emit('delete-catalog', id, () => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          })
          node.remove() // tree 上删除节点
        })
      })
    },
    newElement(node, parent_id) {
      node.expand() // 展开当前节点
      this.addNewNode = true // 设置 添加新节点标记 为 true
      this.currentNode = node // 设置当前节点
      this.$emit('add-element', parent_id)
    },
    copyElement(node, id) {
      this.addNewNode = true // 设置 添加新节点标记 为 true
      this.currentNode = node.parent // 设置当前节点
      this.$emit('copy-element', node.parent.id, id)
    },
    delElement(node, id) {
      this.currentNode = node // 设置当前节点
      this.addNewNode = true

      this.$confirm('是否删除 "' + node.label + '" ?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$emit('delete-element', id, () => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          })
          node.remove() // tree 上删除节点
        })
      })
    },
    // 当 tree 节点选中改变后触发此方法
    handleTreeNodeChange(data, node) {
      if (this.addNewNode) {
        this.addNewNode = false // 如果是添加节点，设置为 false（不是新增节点）
      } else { // 如果不是新增节点，则显示 控件 或 目录 信息
        // 由于点击 tree 节点后边的添加按钮时也会触发 change 事件，所以点击添加按钮不需要改当前节点信息，只有点击一个用例的时候才会改变当前节点信息
        this.currentNodeData = data
        this.currentNode = node

        if (data.isCatalog) {
          this.$emit('choose-catalog', data.id)
        } else {
          this.$emit('choose-element', data.id)
        }
      }
    },
    // 当 tree 拖拽成功完成时触发的事件
    treeHandleDrop(draggingNode, dropNode, dropType, ev) {
      draggingNode.data.parentId = dropNode.data.parentId // 防止 源 和 目标 不是同一个父分类，将 源 的父分类设置成目标的
      this.$emit('tree-sort', draggingNode, dropNode, dropType)
    },
    // 当 tree 拖拽时判定目标节点能否被放置
    treeAllowDrop(draggingNode, dropNode, type) {
      if ((draggingNode.data.isCatalog && !dropNode.data.isCatalog) || // 拖拽节点 是目录，落下节点 是元素，则目标节点前、子、后都不允许
        (!draggingNode.data.isCatalog && dropNode.data.isCatalog && type === 'prev') || // 拖拽节点 是元素，落下节点 是目录，则目标节点前不允许
        (!draggingNode.data.isCatalog && dropNode.data.isCatalog && type === 'next') || // 拖拽节点 是元素，落下节点 是目录，则目标节点后不允许
        (!draggingNode.data.isCatalog && !dropNode.data.isCatalog && type === 'inner')) { // 拖拽节点 和 落下节点 都是元素，不允许挪动成子节点
        return false
      } else {
        return true
      }
    },
    renderTreeContent(h, { node, data, store }) {
      if (data.isCatalog) {
        // 渲染 tree 的分类数据
        return (
          <span class='custom-tree-node'>
            <li class='el-icon-folder-opened' style='color: #FFCC33;'></li>
            <span style='font-size: 14px'>「{data.id}」{node.label}</span>
            <span v-show={!this.readonly}>
              <el-tooltip content='刷新' placement='top' open-delay={500}>
                <el-button style='margin-left: 10px;' size='mini' type='text' onClick={() => this.reload(node, data.id)}><li class='el-icon-refresh'></li></el-button>
              </el-tooltip>
              <el-tooltip content={`新建${this.catalogLabel}`} placement='top' open-delay={500}>
                <el-button style='margin-left: 2px;' size='mini' type='text' onClick={() => this.newCatalog(node, data.id)}><li class='el-icon-folder-add'></li></el-button>
              </el-tooltip>
              <el-tooltip content={`新建${this.elementLabel}`} placement='top' open-delay={500}>
                <el-button style='margin-left: 2px;' size='mini' type='text' onClick={() => this.newElement(node, data.id)}><li class='el-icon-document-add'/></el-button>
              </el-tooltip>
              <el-tooltip content='复制' placement='top' open-delay={500}>
                <el-button style='margin-left: 2px;' size='mini' type='text' onClick={() => this.copyCatalog(node, data.id)}><li class='el-icon-copy-document'></li></el-button>
              </el-tooltip>
              <el-tooltip content='删除' placement='top' open-delay={500}>
                <el-button style='margin-left: 2px;' size='mini' type='text' onClick={() => this.delCatalog(node, data.id)}><li class='el-icon-delete'></li></el-button>
              </el-tooltip>
            </span>
          </span>
        )
      } else {
        node.isLeaf = true // 控件设置没有子节点
        // 渲染 tree 的控件数据
        return (
          <span class='custom-tree-node'>
            <span
              class={ data.icon === 'pass' ? 'el-icon-success' : data.icon === 'fail' ? 'el-icon-error' : data.icon === 'stop' ? 'el-icon-info' : 'el-icon-document' }
              style={ data.icon === 'pass' ? 'color: #67C23A;' : data.icon === 'fail' ? 'color: #F56C6C;' : data.icon === 'stop' ? 'color: #909399;' : 'font-size: 14px' }
            ></span>
            <span style='font-size: 14px;'>「{data.id}」{node.label}{data.priority === 0 ? '「P0' : data.priority === 1 ? '「P1' : data.priority === 2 ? '「P2' : data.priority === 3 ? '「P3' : ''}{data.editStatus === 1 ? ' / 编写中」' : data.editStatus === 2 ? ' / 等待评审]' : data.editStatus === 3 ? ' / 评审不通过]' : data.editStatus === 4 ? ' / 评审通过]' : ''}</span>
            <span v-show={!this.readonly}>
              <el-tooltip content='复制' placement='top' open-delay={500}>
                <el-button style='margin-left: 10px;' size='mini' type='text' onClick={() => this.copyElement(node, data.id)}><li class='el-icon-copy-document'></li></el-button>
              </el-tooltip>
              <el-tooltip content='删除' placement='top' open-delay={500}>
                <el-button style='margin-left: 2px;' size='mini' type='text' onClick={() => this.delElement(node, data.id)}><li class='el-icon-delete'></li></el-button>
              </el-tooltip>
            </span>
          </span>
        )
      }
    },
    // 装载 tree 节点数据
    loadTreeNode(node, resolve) {
      if (node.level === 0) {
        // 保存 tree 根节点，为了后续刷新 tree 使用
        this.node_had = node
        this.resolve_had = resolve
        this.$emit('tree-content', 0, resolve) // 如果是根节点，加载 parentID=0 的
      } else {
        setTimeout(() => {
          this.$emit('tree-content', node.data.id, resolve)
        }, 300)
      }
    },
    // 更新当前树节点数据
    reload(node, parent_id) {
      this.addNewNode = true // 新增节点
      this.currentNode = node // 设置当前节点

      // 更新当前树节点数据
      node.loaded = false
      node.loadData() // 重新查询当前节点的所有子元素
      this.$emit('reload-catalog')
    },
    // 获取tree节点勾选的值（只返回勾选的 元素）
    handleAllCheckedData() {
      const checkDate = this.$refs.elTree.getCheckedNodes()
      const arr = []
      for (var i = 0; i < checkDate.length; i++) {
        if (checkDate[i].isCatalog === false) {
          arr.push(checkDate[i])
        }
      }
      this.$emit('data', arr)
    },
    /**
     * 透传调用 tree 的 setCheckedKeys 方法，通过 keys 设置目前勾选的节点
     * @param keys 勾选节点的 key 的数组
     * @param leafOnly boolean 类型的参数，若为 true 则仅设置叶子节点的选中状态，默认值为 false
     */
    setCheckedKeys(keys, leafOnly) {
      return this.$refs.elTree.setCheckedKeys(keys, leafOnly)
    },
    /**
     * 透传调用 tree 的 getCheckedNodes 方法，返回目前被选中的节点所组成的数组
     * @param leafOnly 是否只返回叶子节点，默认值为 false
     * @param includeHalfChecked 返回数据中是否包含半选节点，默认值为 false
     */
    getCheckedNodes(leafOnly, includeHalfChecked) {
      if (leafOnly === undefined) {
        leafOnly = false
      }
      if (includeHalfChecked === undefined) {
        includeHalfChecked = false
      }
      return this.$refs.elTree.getCheckedNodes(leafOnly, includeHalfChecked)
    },
    // 透传调用 tree 的 getHalfCheckedKeys 方法，返回目前半选中的节点的 key 所组成的数组
    getHalfCheckedKeys() {
      return this.$refs.elTree.getHalfCheckedKeys()
    }
  }
}
</script>
